package com.easylinkin.sm.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.easylinkin.sm.constant.LogConstant;
import com.easylinkin.sm.constant.RoleConstant.Data;
import com.easylinkin.sm.constant.RoleConstant.Error;
import com.easylinkin.sm.entity.Department;
import com.easylinkin.sm.entity.Privilege;
import com.easylinkin.sm.entity.Role;
import com.easylinkin.sm.mapper.RoleMapper;
import com.easylinkin.sm.repository.DepartmentRepository;
import com.easylinkin.sm.repository.PrivilegeRepository;
import com.easylinkin.sm.repository.RoleRepository;
import com.easylinkin.sm.service.RoleService;
import com.easylinkin.sm.util.DataPermissionUtils;

import lombok.extern.slf4j.Slf4j;
import site.morn.boot.data.DisplayableServiceSupport;
import site.morn.boot.jpa.SpecificationBuilder;
import site.morn.core.CriteriaMap;
import site.morn.exception.ApplicationMessages;
import site.morn.framework.context.AccountContext;
import site.morn.framework.context.dto.BaseLoginInfo;
import site.morn.framework.entity.BaseRole;
import site.morn.log.OperateArguments;
import site.morn.rest.RestModel;

/**
 * 角色服务实现
 *
 * @author majintao
 * @since 1.0.0, 2019-08-19-9:34
 */
@Slf4j
@Service
@Transactional
public class RoleServiceImpl extends DisplayableServiceSupport<Role, Long, RoleRepository>
implements RoleService {

  @Autowired
  DepartmentRepository departmentRepository;

  @Autowired
  PrivilegeRepository privilegeRepository;

  @Autowired
  RoleRepository roleRepository;
  
  @Autowired
  private RoleMapper roleMapper;

  @Override
  public Collection<Role> getByUserId(Long userId) {
	  List<Role> roles = roleMapper.findByUsersId(userId);
	  return roles;
//    return repository().findByUsersId(userId);
  }

  @Override
  public Role get(Long id) {
    Role role = super.get(id);
    Optional<Department> optional = departmentRepository.findById(role.getDepartmentId());
    if (optional.isPresent()) {
      role.setDepartmentName(optional.get().getName());
    }
    Set<Privilege> privilegeList = new HashSet();
    role.getPrivileges().stream().forEach(privilegeList::add);
    role.setPrivileges(privilegeList);
    return role;
  }

  @Override
  public <S extends Role> S add(RestModel<S> restModel) {
    return this.addOrUpdate(restModel);
  }

  @Override
  public <S extends Role> S update(RestModel<S> restModel) {
    return this.addOrUpdate(restModel);
  }

  private <S extends Role> S addOrUpdate(RestModel<S> restModel){
    S role = restModel.getModel();
    Role findRole = repository().findFirstByNameAndDepartmentIdAndDisplay(role.getName(), role.getDepartmentId(), true);
    if (findRole != null && !findRole.getId().equals(role.getId())) {
      throw ApplicationMessages.translate(Error.ROLE_NAME_REPEAT).exception();
    } else {
      role.setDisplay(true);
      this.setPrivileges(role,restModel.getAttach());
      repository().save(role);
    }
    OperateArguments.add(role.getName());
    return role;
  }

  private void setPrivileges(Role role, CriteriaMap criteriaMap){
    String idStr = criteriaMap.getString(Data.PRIVILEGEIDS);
    Set<Privilege> privileges = Collections.emptySet();
    if (!StringUtils.isEmpty(idStr)) {
      String[] ids = criteriaMap.getString(Data.PRIVILEGEIDS).split(Data.SEPARATOR_COMMA);
      List<Long> idList = Arrays.asList(ids).stream().map(Long::valueOf).collect(Collectors.toList());
      privileges = privilegeRepository.findByIdIn(idList);
    }
    role.setPrivileges(privileges);
  }

  @Override
  protected Specification<Role> searchSpecification(Role model, CriteriaMap attach) {
   List<Long>  roleIds = this.getCurrentRoleIds();
    return SpecificationBuilder.withParameter(model, attach)
        .specification((reference, predicate, condition) -> {
          Root root = reference.root();
          CriteriaBuilder builder = reference.builder();
          reference.query().orderBy(builder.desc(root.get(LogConstant.Fields.MODIFYTIME)));
          predicate.appendAnd(condition.contain(BaseRole.Fields.name));
          if (!roleIds.isEmpty()) {
            predicate.appendAnd(reference.path().get("id").in(roleIds).not()); // 过滤当前用户所属角色
          }
        }).and(DataPermissionUtils.subOrganizations(model.getDepartmentId()))
        .and(DataPermissionUtils.display());
  }

  @Override
  public List<Role> getRolesForCurrentUser(Long departmentId) {
    // 默认查询当前组织机构下的角色列表数据
    Department department = AccountContext.activeDepartment();
    if (!departmentId.equals(-1L)) {
      Optional<Department> optional = departmentRepository.findById(departmentId);
      if (optional.isPresent()) {
        department = optional.get();
      }
    }
    List<Department> list = departmentRepository
        .findAllBySearchCodeLikeAndDisplay("%" + department.getSearchCode() + "%", true);
    Set<Long> departmentIds = list.stream().map(Department::getId).collect(Collectors.toSet());
    return repository().findAllByDepartmentIdInAndIdNotInAndDisplay(departmentIds, this.getCurrentRoleIds(), true);
  }

  @Override
  public Collection<Role> getRolesByDepartmentId(Long departmentId) {
    return repository().findRolesByDepartmentIdAndDisplay(departmentId, true);
  }

  /**
   * 获取当前登录用户的角色IDS
   * @return
   */
  private List<Long> getCurrentRoleIds() {
    List<Long> roleIds = new ArrayList();
    BaseLoginInfo baseLoginInfo = AccountContext.loginInfo();
    List<Role> roles = (List<Role>)baseLoginInfo.getRoles();
    if (null != roles && !roles.isEmpty()){
      roleIds = roles.stream().map(Role::getId).collect(Collectors.toList());
    }
    return roleIds;
  }


}
